/*
 * Copyright 2013 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.aplink.generic.google.maps;

/**
 * Utility functions that are used my both PolyUtil and SphericalUtil.
 */
class MathUtil {
	/**
	 * The earth's radius, in meters. Mean radius as defined by IUGG.
	 */
	static final double EARTH_RADIUS = 6371009;

	/**
	 * Computes inverse haversine. Has good numerical stability around 0.
	 * arcHav(x) == acos(1 - 2 * x) == 2 * asin(sqrt(x)). The argument must be
	 * in [0, 1], and the result is positive.
	 */
	static double arcHav(final double x) {
		return 2 * Math.asin(Math.sqrt(x));
	}

	/**
	 * Restrict x to the range [low, high].
	 */
	static double clamp(final double x, final double low, final double high) {
		return x < low ? low : (x > high ? high : x);
	}

	/**
	 * Returns haversine(angle-in-radians). hav(x) == (1 - cos(x)) / 2 == sin(x
	 * / 2)^2.
	 */
	static double hav(final double x) {
		final double sinHalf = Math.sin(x * 0.5);
		return sinHalf * sinHalf;
	}

	/**
	 * Returns hav() of distance from (lat1, lng1) to (lat2, lng2) on the unit
	 * sphere.
	 */
	static double havDistance(final double lat1, final double lat2,
			final double dLng) {
		return MathUtil.hav(lat1 - lat2)
				+ (MathUtil.hav(dLng) * Math.cos(lat1) * Math.cos(lat2));
	}

	// Returns hav(asin(x)).
	static double havFromSin(final double x) {
		final double x2 = x * x;
		return (x2 / (1 + Math.sqrt(1 - x2))) * .5;
	}

	/**
	 * Returns latitude from mercator Y.
	 */
	static double inverseMercator(final double y) {
		return (2 * Math.atan(Math.exp(y))) - (Math.PI / 2);
	}

	/**
	 * Returns mercator Y corresponding to latitude. See
	 * http://en.wikipedia.org/wiki/Mercator_projection .
	 */
	static double mercator(final double lat) {
		return Math.log(Math.tan((lat * 0.5) + (Math.PI / 4)));
	}

	/**
	 * Returns the non-negative remainder of x / m.
	 *
	 * @param x
	 *            The operand.
	 * @param m
	 *            The modulus.
	 */
	static double mod(final double x, final double m) {
		return ((x % m) + m) % m;
	}

	// Given h==hav(x), returns sin(abs(x)).
	static double sinFromHav(final double h) {
		return 2 * Math.sqrt(h * (1 - h));
	}

	// Returns sin(arcHav(x) + arcHav(y)).
	static double sinSumFromHav(final double x, final double y) {
		final double a = Math.sqrt(x * (1 - x));
		final double b = Math.sqrt(y * (1 - y));
		return 2 * ((a + b) - (2 * ((a * y) + (b * x))));
	}

	/**
	 * Wraps the given value into the inclusive-exclusive interval between min
	 * and max.
	 *
	 * @param n
	 *            The value to wrap.
	 * @param min
	 *            The minimum.
	 * @param max
	 *            The maximum.
	 */
	static double wrap(final double n, final double min, final double max) {
		return ((n >= min) && (n < max)) ? n : (MathUtil
				.mod(n - min, max - min) + min);
	}
}
